package com.lpb.spring.lpbspring.aop.proxy.weaving;

import com.lpb.spring.lpbspring.aop.advice.advices.*;
import com.lpb.spring.lpbspring.ioc.BeanFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

/**
 * @author linpengbo
 * @date 2022/12/10
 */
public class AopAdviceWeavingChain {

    private Object target;

    private Method method;

    private Object[] args;

    private List<Object> adviceList;

    private BeanFactory beanFactory;

    public AopAdviceWeavingChain(Object target, Method method, Object[] args, List<Object> adviceList, BeanFactory beanFactory) {
        this.target = target;
        this.method = method;
        this.args = args;
        this.adviceList = adviceList;
        this.beanFactory = beanFactory;
    }


    private static Method invokeMethod;

    static {
        try {
            invokeMethod = AopAdviceWeavingChain.class.getDeclaredMethod("invoke", null);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    private int index;

    public Object invoke() throws Exception {
        if (index < adviceList.size()) {
            Object advice = adviceList.get(index++);
            if (advice instanceof MethodBeforeAdvice) {
                MethodBeforeAdvice beforeAdvice = (MethodBeforeAdvice) advice;
                beforeAdvice.invoke(method, args, target);
            } else if (advice instanceof MethodInterceptor) {
                MethodInterceptor interceptor = (MethodInterceptor) advice;
                //先执行 原方法前的增强 再执行原方法（在invokeMethod里包含着） 最后执行原方法后的增强
                return interceptor.invoke(invokeMethod, null, this);
            } else if (advice instanceof MethodAfterAdvice) {
                MethodAfterAdvice afterAdvice = (MethodAfterAdvice) advice;
                Object retrunValue = null;
                try {
                    retrunValue = this.invoke();
                } finally {
                    afterAdvice.invoke(retrunValue, method, args, target);
                }
                return retrunValue;
            } else if (advice instanceof AfterReturningAdvice) {
                AfterReturningAdvice afterReturningAdvice = (AfterReturningAdvice) advice;
                Object retrunValue = this.invoke();
                afterReturningAdvice.invoke(retrunValue, method, args, target);
                return retrunValue;
            } else if (advice instanceof ThrowsAdvice) {
                ThrowsAdvice throwsAdvice = (ThrowsAdvice) advice;
                try {
                    return  this.invoke();
                } catch (Exception e) {
                    throwsAdvice.afterThrowing(method, args, target, e);
                }
            }
            return this.invoke();
        } else {
            return this.method.invoke(target, args);
        }
    }


}
